//NewLidarPro by YANG Dongwei, student of LI Chengcai

//Let's make the symbols clear first:
//PI	=	3.1415926...
//C		=	the Light Speed

//****M	~	Molecule
//****A	~	Aerosol
//Beta	~	Back Scatter Coefficient
//BetaM	=	Molecule Back Scatter Coefficient
//BetaA	=	Aerosol Back Scatter Coefficient
//Sigma	~	Extinction Coefficient
//SigmaM=	Molecule Extinction Coefficient
//SigmaA=	Aerosol Extinction Coefficient
//Sm/Sa =   back scattering ratio of molecule/aerosol
//z		~	Height
//Alpha	~	Elevation Angle of the Lidar, is it still 75 degrees?
//SinAlpha  sin(Alpha)
//CosAlpha  cos(Alpha) to make the calculation faster.
//AOD	~	Aerosol Optical Depth
//USEABLESTARTINDEX		as we know the first several data in a shot are duds
//FIRSTDATAINDEX		this index means which data is the really the first 7.5m/15m data...

#include <iostream>
#include <iomanip>
#include <fstream>
#include <string>
#include <cmath>
#include <cstdlib>
#include <vector>
#include <sstream>
#include <cstring>
using namespace std;

#ifndef NEW_LIDAR_PRO_CONSTANTS
#define NEW_LIDAR_PRO_CONSTANTS

//------------Options -- Edit here if any thing changes... --------

//about the operating system...
	const string SLASH="/";            //for windows, set it "\\" (take care: 2 '\'s); for linux , set it "/"
	const string DIRorLS= "ls";		//for windows, set it "dir"; for linux, "ls" of course.

//about where the file: "setting.ini" is. (also for the overlap, afterpulse and atmos profile files...
	const string WHERE_IS_INI = "./";  //never forget to end it with a "/"(Linux) or "\\"(Windows);

//about the lidar
	const float SM=8.37758 ;         //Sm=moleculeExtinctionCoefficient/moleculeBackScatterCoefficient=SigmaM/BetaM=8*PI/3
	const float LAMBDA = 523.0 ;  //wave length of the lidar laser. 523nm as the old Lidar?
	const float RHO_N =0.0284 ;   //depolarization factor, actually 0.0284 is for 550nm. but I didn't find a preciser one...

	const int USEABLESTARTINDEX_7_5M=22; //Useable Data starts from which one?
	const int FIRSTDATAINDEX_7_5M=9;    //e.g. for 7.5m resolution, data[FIRSTDATAINDEX] means 3.75m height, etc
	const int USEABLESTARTINDEX_15M=12;
	const int FIRSTDATAINDEX_15M=4;     
	const int USEABLESTARTINDEX_30M=6; //guess !!!
	const int FIRSTDATAINDEX_30M=2; //guess !!!
	//FIRSTDATAINDEX should NEVER be greater than USEABLESTARTINDEX

//about the datafile format
	//index in the analogData[]. If the data format changes, just edit here and try if this program can still handle it...
	const int ENERGYINDEX=0;        
	const int TEMPDETECTORINDEX=1;
	const int TEMPBOXINDEX=2;
	const int TEMPLASERINDEX=3;
	const int DEADTIMECORRECTEDINDEX=4;
	const int BACKGROUNDINDEX=5;
	const int CLOUDBASEHEIGHTINDEX1=6;

	const string EXT="??W";              //the extension name of the data file.
	const int HEADSIZE=76;          //take care of the problem of memery alignment... see the "rubbish" in class OneLine
	const int MAXANALOGNUM=16;      //now the used number is 11, but who knows whether it will change
	const int MAXBINSNUM=8000;      //should be able to handle 7.5m/60km
	const int MAXCONTAINERSIZE=1000; //OK, better not do average over more than 2 hours

//some that may never been used..
	const int ATMOSMODELNUM_DEFAULT=6;     //1=Tropical atm, 2=midlatitude summer, 3=midlat winter, 4=subarctic summer, 5=subarc winter, 6&etc=1976 US, as in LOWTRAN but 999=new profile 
	const float SA_DEFAULT=25.0;			//usually this doesn't work because we are using the one from the "setting.ini" file
	const float LIDARCONST_DEFAULT=18.0;    //and this one either...
	const string INPUTDATAPATH_DEFAULT = ""; //and the following several defaults...
	const string OUTPUTDATAPATH_DEFAULT = "";

//some other options...
	const bool OUTPUTSIZE_IN_IMG_TXT= false; //if you decide to add a parametre in the output file, make it true
	const double PI=3.14159265;   
	const double C=2.99793e8;     //unit: m/s... seems never been used in this program...
	const float LOGLOWLIMIT=1e-10;    //to make log(somenumber) never fails: if somenumber < LOGLOWLIMIT, somenumber=LOGLOWLIMIT
	const int SUCCEED=1;
	const int FAILED=0;

	//fLaser
	//Experimenting... not finished yet
	const float MAXPOSSIBLECLOUDHEIGHT=20.0;
	const float MAXPOSSIBLEMIXLAYERHEIGHT=3.0; 
//-----------------End Of Options-------------------

#endif

//aux functions
bool IsLeapYear(int YYYY);
int YYYYMMDD2Int(int YYYY, int MM, int DD);   //return the daynum since 20000101, e.g. 20000101=0
int YYYYMMDD2Int(string str);
int YYYYMMDDhhmmss2Int(int YYYY, int MM, int DD, int hh, int mm, int ss); //take care of the long int limit: 2147483647!!  should be OK not until the year 2064
int YYYYMMDDhhmmss2Int(string str);
string Int2YYYYMMDDhhmmss(int secnum);
string Int2YYYYMMDD(int daynum);
//You may need to rewrite this function if the file name format changes.
bool IsTheFileInTheTaskTimePeriod(string fileFullName, int taskStartTimeInt, int taskEndTimeInt);

double interp(double x1, double y1, double x2, double y2, double x);
void OnHelp();
struct DistDataSet
{
	float dist;
	float elem1;
	float elem2;
};

struct LidarProArg
{
	bool b_i;		//output normalized relative backscattering signal image data
	bool b_a;		//output aerosol extinction coefficient image data
	bool b_t;		//output time series aerosol extinction coefficient data at a given 'height'
	bool b_p;		//no longer available
	bool b_d;		//output aerosol optical depth below a given 'limit'
	bool b_f;		//output float data from 0 to 1 instead of color index from 0 to 255.
	bool b_r;		//no longer available
	bool b_c;		//test: output cloud bottom
	bool b_m;		//not finished: output mixing layer height.
	bool b_v;      //average minutes
	bool b_S;		//get sa
	bool b_L;		//get lidar Const
	bool b_O;		//get Overlap
	bool b_U;		//get Afterpulse;
	bool b_Y;       //Raw data
	bool b_G;       //original data, not converted to 0-255, or 0-1 float..

	float lidarpro_height;   //the given 'height' with b_t
	float lidarpro_limit;   //the given 'limit' with b_d
	int startTimeSecInt;   //task start time converted to seconds from 2000.01.01
	int endTimeSecInt;     //task end time 
	int averMinInt;        //average minutes
};
struct LidarINI
{
	float sa;       //lidar ratio
	float lidarConst;
	int atmosModel;
	float alpha;	//elevation angle
	char inputPath[256];
	char outputPath[256];
	int outputSize;
	float farrestAfterpulseAverage;
	float AODcalculationHeightLimit;
	float cloudDetectThreshold;
	float cloudDetectThreshold2;
	float cloudDetectThreshold3;
	float mixlayerDetectThreshold;
	float mixlayerDetectThreshold2;
	float mixlayerDetectThreshold3;
	float IMG_BACK_PARA_1;
	float IMG_BACK_PARA_2;
	float IMG_EXTC_PARA_1;
	float IMG_EXTC_PARA_2;
};
LidarINI LoadINI(string iniFileName);
LidarProArg AnalysisArg(int ac, char *av[]);

class OneLine
{
//friend class FileReader;
//  friend int FileReader::FileTest(string newFileFullName);
public:

//Do keep these together, because I used memcpy() function...=============================================================
    unsigned char iSoftwareVersion;
    unsigned char iHardwareVersion;
    // HL I know this is stupid.  But now I will replace hardwareID with DataID.
    // Since we cannot read out hardware ID right now.
    // DataID will be set by software (computer) instead of hardware.
    // This is suggestion in future: update firmware and FPGA, leave hardware ID in FPGA.
    //int iHardwareID;
//    int iDataID;
    // Add in new field. On 2nd thought, this is deleted.  This field is moved into analog field
    //int iDeadTimeCorrected;
    union {
    unsigned short iDate[3];
    struct {
    unsigned short year, month, day;};
    };
	    union {
    unsigned short iTime[3];
    struct {
    unsigned short hours, minutes, seconds; }; };  // - at the end of profile
    
    //int iNumberOfChannel;
    //int rubbish;    //.....
    
    unsigned short dFrequencyOfLaser; // Hz
    unsigned int lNumberOfShotsPerAverage;
	float energy_monitor;
	float detect_temp;
	float box_temp;
	float laser_temp;
	unsigned int cbh;
	float background;
    unsigned int dTimeOfBinNS; // nano sec 
	unsigned int maxaltitude;
	unsigned int dtc;
	double reserved;
    int iNumberOfAnalog; // energy monitor, temp. of detector, temp. of box, temp. of laser
	double dTimeOfBin; 
//End Keep together==================================================================
 
	float analogData[MAXANALOGNUM];      
    float data[MAXBINSNUM];     //the data received by the LIDAR are here~~
//	float extinctionData[MAXBINSNUM]; //this is not necessary, really, cuz we can record the extinction data in another OneLine object~
    //Aux Variables..

//aux variables...
    int headSize;		//Now it should be 68, packed memory alignment
    int binsNum;		//Typical number is 4000 or so?, for 30m/60km, it's 2000.
	int binsLengthX;	//7.5m: binsLengthX=1 , 15m: binsLengthX=2, 30m: binsLengthX=4,and so on...
	int averageTime;    //unit : s
	bool bIsSigma;   //
	bool bIsNormalized; //
	int maxIndexOfSigmaedData; 



//static data members
public:
	static float overlapCoefficient[MAXBINSNUM];
	static int overlapEndIndex;
	static float afterpulse[MAXBINSNUM];
	static int afterpulseEndIndex;
	static float betaM[MAXBINSNUM]; //BetaM=Molecule Back Scatter Coefficient
	static float integratedBetaM[MAXBINSNUM]; //for faster calculation
	static float TProfile[MAXBINSNUM];
	static float PProfile[MAXBINSNUM];
//	static float sa;      //sa=sigmaA/betaA
//	static float lidarConst;
	static int staticBinsLengthX;
	static int USEABLESTARTINDEX;
	static int FIRSTDATAINDEX;
//	static float cloudDetectThreshold;
	static LidarINI ini;
	static float SINALPHA;
	static float COSALPHA;
	
public:
	OneLine(void);
	~OneLine(void){};
    OneLine(const OneLine &oldLine);

	OneLine operator = (OneLine oldLine);
	OneLine operator + (OneLine oldLine);
	OneLine operator / (int div);

//Aux
	int GetTimeInt(){return YYYYMMDDhhmmss2Int(year,month,day,hours,minutes,seconds);}
	int GetDayInt(){return YYYYMMDD2Int(year,month,day);}
	int GetAverageMinInt(){return (int)(averageTime/60.0+0.5);}
	string GetTimeString(){return Int2YYYYMMDDhhmmss(GetTimeInt());}
	string GetDayString(){return Int2YYYYMMDD(GetDayInt());}
	string GetAverageMinString();

	void SetCloudDetectThreshold(float newThres){ini.cloudDetectThreshold=newThres;}
	void SetSa(float newSa){ini.sa=newSa;}
	void SetLidarConst(float newLC){ini.lidarConst=newLC;}
	float IndexToHeight(int index);
	float IndexToDistance(int index);
	int HeightToIndex(float height);
	int DistanceToIndex(float distance);
//Initializing==========================
//==============Initialing the statics==============
	//Let's fix the data format, to make it easy to read for both human and computer
	//the first 3 line shall be left for comment, do WRITE SOMETHING on it...
	//and the 4th line contains the data number and binsLengthX (Profile file doesn't have binsLengthX)
	//then following lines are data like this 
	//[index number(at 7.5m	resolution as the Lidar data)/AtmosProfile doesn't have this term]	[distance(in km)] [data]/[data1 data2...]
	//the Overlap Coefficient file ext shall be .ovlp
	//the Afterpulse Data file ext shall be .aftp
	//the AtmosProfile file ext shall be .aprf, 
	int LoadOverlapCoefficient(string fileName="", int newBinsLengthX=4);															
	int LoadAfterpulse(string fileName="", int newBinsLengthX=4);
	//1=Tropical atm, 2=midlatitude summer, 3=midlat winter, 4=subarctic summer, 5=subarc winter, 6&etc=1976 US, as in LOWTRAN but 999=new profile 
	int LoadAtmosProfile(int ProfileNo, string fileName="",int newBinsLengthX=4);  //
	int	CalculateBetaM();
	int IntegrateBetaM();
	int Initialize(LidarINI newINI, int newBinsLengthX=4);

//Functions==============================
	int NormalizeData();
	float GetLidarConst(float AOD, float lowHeight, float highHeight, bool bOutputDetail, string outFileName="");
	float GetSa(float AOD, int neededHeightIndex =2000 ,string outFileName="", float minSa=10.0, float maxSa=50.0);
	float GetAOD(int neededHeightIndex=0,float newSa=-1,float newLidarConst=-1);
	OneLine GetSigmaA(int neededHeightIndex=0,float newSa=-1,float newLidarConst=-1 );
	int GetOverlapCoefficient(float linearStartDistance, float linearEndDistance, string outFileName="");
	int GetCloudBottomIndex(float newThreshold, float newThreshold2, float newThreshold3);
	int GetMixlayerIndex(float newThreshold, float newThreshold2, float newThreshold3);  //not finished yet...
	
//IOs===================================
    int GetFromFile(fstream &inFile, int newHeadSize, int newAnalogNum, int newBinsNum,int pos, bool newbIsNormalized=false, bool newbIsSigma=false);
    
	int WriteDetail(ostream &outStream);  //these 3 functions are just for test
    int WriteFormat(ostream &outStream);  //to be deleted
	void DoSomething(ostream &strm){strm<<"Line "<<year<<"/"<<month<<"/"<<day<<" - "<<hours<<":"<<minutes<<":"<<seconds<<" is doing sth... "<<endl;}

	int WriteAtmosProfile(ostream &outStream);
	int WriteBetaM(ostream &outStream);
	int WriteOverlapCoefficient(ostream &outStream);
	int WriteAfterpulse(string outFileName="");
	int WriteDataToAfterpulse(string outFileName="");
	int WriteDataAsLidarPro(ostream &outStream,int recNum, int outputSize=1440, bool outputFloat=false, bool outputOriginal=false);
	int WriteSigmaATimeSerie(ostream &outStream, int heightIndex);
	int WriteAODTimeSerie(ostream &outStream, int limitIndex);
	int WriteCloudBottomTimeSerie(ostream &outStream);
	int WriteMixLayerTimeSerie(ostream &outStream);
	int WriteAsItIs(string outFileName="");
	
} __attribute__((packed));


class FileReader
{
public:
	FileReader(){};
    ~FileReader(){if(file.is_open()) file.close();}
    FileReader(string newFileFullName){SwitchToFile(newFileFullName);}
    
public: 
    int SwitchToFile(string newFileFullName);
    int FileTest(string newFileFullName);
    int FileNameAnalysis(string newFileFullName);
    
    int ReadToLine(OneLine *pLine, int indexOfBlockInFile);

	void ShowInfo();

	friend class Container;
public:
    fstream file;
    string fileFullName;
    string filePath;
    string fileName;
    string fileNameNoExt;
    
    int fileSize;
    int fileDataHeadSize;
    int fileDataAnalogNum;
    int fileDataBinsNum;
    int fileDataBlockSize;
    int fileDataBlockNum;

};
class Container
{
public:
    Container(int num) {lineList=new OneLine[num]();maxSize=num;usedSize=0;}
    ~Container(void){delete[] lineList;}

public:
    int SetData(int index, OneLine newLine);
	int AddData(OneLine newLine);
	int InsertData(int index, OneLine newLine);
	int DeleteData(int index);
	int ClearAll();
    OneLine GetData(int index);


	int Time2Index(int time);
    OneLine Average(int indexStart, int indexEnd);


    friend class FileReader;
    
public:
    int maxSize;
    int usedSize;
	
public:
    OneLine *lineList;
	FileReader fReader;
};



class TaskManager
{
public:	
	TaskManager(struct LidarProArg arg ,struct LidarINI ini);

	~TaskManager();
	
public:
	int Loop();
	int CloseAllFiles();

	int TestArg();
	int DoTask();
	int LidarProTask();
	
private:
//	int taskStartTime;  //seconds from 2000/01/01-00:00:00
//	int taskEndTime;
//	int averageMinutes;

//	float taskSa;
//	float taskLidarConst;
//	int taskAtmosModelNum;
//	string inFilePath;
//	string outFilePath;
//	int outputSize;
//	float cloudDetectThreshold;

	LidarProArg taskArg; //got from the args...
	LidarINI taskINI;  //got from the ini file...	

	bool bOneLineInitialed;
	int taskBinsLengthX;

	ifstream listFile;

	OneLine tempLine;		//this is where the line data goes before it enters the pool
	FileReader fReader;
	Container *pool;     //contains a lot of lines... mainly for AVERAGE.
	Container *workshop; //contains whatever you're really handling
	Container *expo;     //this is not used in NewLidarPro, but you may need it for further process.
//	Container *auxCTN1, *auxCTN2, *auxCTN3, *auxCTN4; //you might need these;
	
	string fileNameTime; 
	ofstream fileI;		//backscatter
	ofstream fileA;		//aerosol extinction
	ofstream fileT;		//time series aerosol extinction at a given height
	ofstream fileD;		//AOD below a limit height
	ofstream fileC;		//cloud botton height
	ofstream fileM;		//mixing layer height

	int countI;  //the output image data need an index...
	int countA;
};


